package com.gitee.sop.layui.adminserver.frameword.init.listener;

import com.gitee.sop.layui.adminserver.module.background.develop.entity.SysFunction;
import com.gitee.sop.layui.adminserver.module.background.develop.service.SysFunctionService;
import com.gitee.sop.layui.adminserver.module.base.util.StringUtil;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 功能菜单加载处理
 *
 * @author: huwei
 * @date: 2019/9/23 18:28
 * @version: 1.0.0
 */
@Slf4j
@Order(99)
@WebListener
public class FunctionLoaderListener implements ServletContextListener {

    @Autowired
    private SysFunctionService sysFunctionService;

    @Resource
    private ApplicationContext applicationContext;

    /**
     * 功能菜单类名命名后缀
     */
    private static final String[] FUNCTION_MENU_CLASS_NAME_SUFFIX = new String[]{"Controller"};
    /**
     * 功能菜单类名前缀
     */
    private static final String FUNCTION_MENU_ROOT_PACKAGE = "com.gitee.sop.layui.adminserver";

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        List<Object> sysFunctionObjectList = listSysFunctionObject();

        final List<SysFunction> sysFunctionList = new ArrayList<>(16);
        sysFunctionObjectList.forEach(x -> {
            List<SysFunction> list = parseSysFunction(x);
            sysFunctionList.addAll(list);
        });
        Map<String, SysFunction> sysFunctionMap = toMap(sysFunctionList);

        List<SysFunction> oldList = sysFunctionService.list();
        Map<String, SysFunction> oldMap = toMap(oldList);

        //新增的
        List<SysFunction> newlyList = new ArrayList<>();
        //已被删除的
        List<SysFunction> deletedList = new ArrayList<>();
        List<Integer> deletedIdList = new ArrayList<>();
        //更新过的
        List<SysFunction> updatedList = new ArrayList<>();

        //新增和更新统计
        sysFunctionMap.forEach((k, newly) -> {
            SysFunction old = oldMap.get(k);
            if (old == null) {
                //新增
                newlyList.add(newly);
            } else if (!old.getFullUri().equals(newly.getFullUri())) {
                //uri变更
                old.setClassUri(newly.getClassUri()).setFnUri(newly.getFnUri()).setFullUri(newly.getFullUri())
                        .setFnUriMethod(newly.getFnUriMethod()).setClassUriMethod(newly.getClassUriMethod())
                        .setResultType(newly.getResultType());
                updatedList.add(old);
            }
        });

        //删除统计 TODO
        oldMap.forEach((k, old) -> {
            if (sysFunctionMap.get(k) == null) {
                deletedList.add(old);
                deletedIdList.add(old.getId());
            }
        });

        //删除
        if (!deletedList.isEmpty()) {
            log.info("---> 删除功能，数量：" + deletedList.size());
            log.info("---> 删除功能:" + deletedList);
            sysFunctionService.removeByIds(deletedIdList);
        }
        //更新
        if (!updatedList.isEmpty()) {
            log.info("---> 更新功能，数量：" + updatedList.size());
            log.info("---> 更新功能：" + updatedList);
            sysFunctionService.updateBatchById(updatedList);
        }
        //新增
        if (!newlyList.isEmpty()) {
            log.info("---> 新增功能，数量：" + newlyList.size());
            log.info("---> 新增功能：" + newlyList);
            sysFunctionService.saveBatch(newlyList);
        }
    }

    /**
     * 转换为功能key到功能的map映射, key由方法sysFunctionSignature(SysFunction) 确定
     *
     * @param list
     * @return
     */
    private Map<String, SysFunction> toMap(List<SysFunction> list) {
        Map<String, SysFunction> res = new HashMap<>(list.size());
        list.forEach(x -> {
            String sysFunctionSignature = sysFunctionSignature(x);
            res.put(sysFunctionSignature, x);
        });
        return res;
    }

    private String sysFunctionSignature(SysFunction sysFunction) {
        StringBuilder sb = new StringBuilder();
        sb.append(sysFunction.getRootPath()).append(sysFunction.getModuleName()).append(sysFunction.getClassDir())
                .append(sysFunction.getClassName()).append(sysFunction.getFnName()).append(sysFunction.getFnArgs());
        return sb.toString();
    }

    private List<SysFunction> parseSysFunction(Object obj) {
        List<SysFunction> result = new ArrayList<>(16);
        SysFunction sysFunction = parseClassInfo(obj.getClass());
        parseMethodInfo(obj.getClass(), sysFunction, result);

        return result;
    }

    private void parseMethodInfo(Class<?> cls, SysFunction classSysFunction, List<SysFunction> result) {
        Method[] methods = cls.getDeclaredMethods();
        for (int i = 0, len = methods.length; i < len; ++i) {
            Method method = methods[i];
            Annotation[] annotations = method.getDeclaredAnnotations();
            UriInfo uriInfo = searchAndParseUriMappingValue(annotations);
            if (uriInfo == null || uriInfo.getUri() == null) {
                continue;
            }
            SysFunction sysFunction = (SysFunction) classSysFunction.clone();
            sysFunction.setFnName(method.getName()).setFnArgs(wrapperParameterTypes(method)).setFnUriMethod(uriInfo.getUriMethod())
                    .setFnUri(StringUtil.concatMark(",", uriInfo.getUri()));
            sysFunction.setFullUri(wrapperFullUri(sysFunction.getClassUri(), sysFunction.getFnUri()));
            sysFunction.setResultType(parseResultType(cls, method));
            result.add(sysFunction);
        }
        Class<?> superClass = cls.getSuperclass();
        if (superClass != null) {
            parseMethodInfo(superClass, classSysFunction, result);
        }
    }

    private Integer parseResultType(Class<?> cls, Method method) {
        RequestBody requestBody = method.getDeclaredAnnotation(RequestBody.class);
        if (requestBody != null) {
            return 1;
        }
        RestController restController = cls.getDeclaredAnnotation(RestController.class);
        if (restController != null) {
            return 1;
        }
        return 0;
    }

    private String wrapperFullUri(String clsUris, String methodUris) {
        if (clsUris == null) {
            return methodUris;
        }
        String[] classUriArray = clsUris.split(",");
        String[] methodUriArray = methodUris.split(",");

        StringBuffer sb = new StringBuffer();
        for (int i = 0, len = classUriArray.length; i < len; ++i) {
            for (int j = 0, jLen = methodUriArray.length; j < jLen; ++j) {
                int index = i * jLen + i * j;
                sb.append(new StringBuilder(classUriArray[i]).append(methodUriArray[j]).toString()).append(",");
            }
        }
        if (sb.length() > 1) {
            sb.delete(sb.length() - 1, sb.length());
        }
        return sb.toString();
    }

    private SysFunction parseClassInfo(Class<?> cls) {
        SysFunction sysFunction = initSysFunction();
        String className = sysFunctionRealClassName(cls.getName());
        //简单类名
        String simpleClassName = className;
        //截取class 上的信息
        if (className.indexOf(".") != -1) {
            simpleClassName = className.substring(className.lastIndexOf(".") + 1);

            String[] classInfos = className.split("\\.");
            if (classInfos.length > 2) {
                sysFunction.setClassDir(classInfos[classInfos.length - 2]);
            }
            if (classInfos.length > 3) {
                sysFunction.setModuleName(classInfos[classInfos.length - 3]);
            }
            if (classInfos.length > 4) {
                sysFunction.setRootPath(className.substring(0, className.indexOf(sysFunction.getModuleName()) - 1));
            }
        }
        sysFunction.setClassName(simpleClassName);

        Class<?> readCls = cls;
        if (!cls.getName().equals(className)) {
            try {
                readCls = Thread.currentThread().getContextClassLoader().loadClass(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        parseClassUri(readCls, sysFunction);

        return sysFunction;
    }

    /**
     * 递归解析类上的uri
     *
     * @param cls         类
     * @param sysFunction 存储变量
     */
    private void parseClassUri(Class<?> cls, SysFunction sysFunction) {

        UriInfo uriInfo = searchAndParseUriMappingValue(cls.getDeclaredAnnotations());
        if (uriInfo == null) {
            return;
        }
        if (sysFunction.getClassUriMethod() == null) {
            sysFunction.setClassUriMethod(uriInfo.getUriMethod());
        }
        String uri = StringUtil.concatMark(",", uriInfo.uri);
        String newUri = sysFunction.getClassUri() == null ? uri : sysFunction.getClassUri().concat(",").concat(uri);
        sysFunction.setClassUri(newUri);

        Class<?> superClass = cls.getSuperclass();
        if (superClass != null) {
            parseClassUri(superClass, sysFunction);
        }
    }

    private UriInfo searchAndParseUriMappingValue(Annotation[] annotations) {
        for (int i = 0, len = annotations.length; i < len; ++i) {
            Annotation annotation = annotations[i];
            Class<?> annotationCls = annotation.annotationType();
            String uriMethod = getUriMethod(annotationCls, annotation);
            if (uriMethod != null) {
                String[] uris = extractAnnotationValue(annotation, annotationCls);
                return new UriInfo().setUri(uris).setUriMethod(uriMethod);
            }
        }
        return null;
    }

    private String[] extractAnnotationValue(Annotation annotation, Class<?> cls) {
        try {
            Method method = cls.getMethod("value", null);
            method.setAccessible(true);
            Object res = method.invoke(annotation, null);
            if (res != null) {
                String[] uris = (String[]) res;
                return uris;
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String getUriMethod(Class<?> annotationCls, Annotation annotation) {
        if (annotationCls.isAssignableFrom(RequestMapping.class)) {
            try {
                Method method = annotationCls.getMethod("method", null);
                method.setAccessible(true);
                Object res = method.invoke(annotation, null);
                if (res != null) {
                    RequestMethod[] requestMethods = (RequestMethod[]) res;
                    if (requestMethods.length > 0) {
                        String[] uris = new String[requestMethods.length];
                        for (int i = 0; i < requestMethods.length; ++i) {
                            uris[i] = requestMethods[i].name();
                        }
                        return StringUtil.concatMark(",", uris);
                    }
                }
                return "get";
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } else if (annotationCls.isAssignableFrom(PostMapping.class)) {
            return "post";
        } else if (annotationCls.isAssignableFrom(GetMapping.class)) {
            return "get";
        }
        return null;
    }

    private SysFunction initSysFunction() {
        SysFunction sysFunction = new SysFunction();
        sysFunction.setPermissionSymbol(0);
        sysFunction.setStatus(1);
        sysFunction.setCreatedTime(new Date());

        return sysFunction;
    }

    private String wrapperParameterTypes(Method method) {
        StringBuffer sb = new StringBuffer();
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0, len = parameterTypes.length; i < len; ++i) {
            sb.append(parameterTypes[i].getName()).append(",");
        }
        return sb.length() == 0 ? null : sb.deleteCharAt(sb.length() - 1).toString();
    }

    private List<Object> listSysFunctionObject() {
        List<Object> res = new ArrayList<>(16);
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (int i = 0, len = beanNames.length; i < len; ++i) {
            String beanName = beanNames[i];
            try {
                Object obj = applicationContext.getBean(beanName);
                if (isSysFunctionObject(obj)) {
                    res.add(obj);
                }
            } catch (Throwable e) {
                //跳过
            }
        }
        return res;
    }

    private boolean isSysFunctionObject(Object obj) {
        String className = obj.getClass().getName();
        className = sysFunctionRealClassName(className);
        for (int i = 0, len = FUNCTION_MENU_CLASS_NAME_SUFFIX.length; i < len; ++i) {
            if (className.endsWith(FUNCTION_MENU_CLASS_NAME_SUFFIX[i]) && className.startsWith(FUNCTION_MENU_ROOT_PACKAGE)) {
                return true;
            }
        }
        return false;
    }

    private String sysFunctionRealClassName(String className) {
        String proxySymbol = "$$";
        int proxySymbolIndex = className.indexOf(proxySymbol);
        return proxySymbolIndex == -1 ? className : className.substring(0, proxySymbolIndex);
    }

    @Data
    @Accessors(chain = true)
    private class UriInfo {
        String[] uri;
        String uriMethod;
    }
}
